/*******************************************************************************
 * Copyright (c) 2012 Philip Douglas.
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 ******************************************************************************/
package net.pdouglas.revisiondroid;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;

import net.pdouglas.revision.TimeFormatter;
import net.pdouglas.revision.Timer;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class TimerService extends Service {
    private static final String TAG = TimerService.class.getSimpleName();
    private static final int NOTIFY_ID = 1;
    private static final int ZERO_RELAX_ALARM_ID = 2;

    private Timer mRevTimer;
    private TimerState mTimerState;
    private java.util.Timer mTimer;

    private long mLastTickTime;
    private volatile boolean mZeroRelaxAlarmPref = false;
    private int sinceBackup = 0;

    private boolean isForeground = false;
    private NotificationManager mNotificationManager;
    private Notification mNotification;
    
    private String BACKUPFILE = "timer_backup";

    @Override
    public IBinder onBind(Intent intent) {
		if (TimerService.class.getName().equals(intent.getAction())) {
			return apiEndpoint;
		} else {
			return null;
		}
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
        mLastTickTime = System.currentTimeMillis();

    	TimerEventHandler eventHandler = new TimerEventHandler(this);
        mTimer = new java.util.Timer("RevisionTimer");
        mRevTimer = readBackupFile(eventHandler);
        mTimerState = new TimerState(mRevTimer);
        mTimer.schedule(this.mUpdateTask, 1000L, 1000L);

        // Notification
        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        int icon = R.drawable.ic_stat_timer;
        CharSequence tickerText = getString(R.string.notify_ticker);

        mNotification = new Notification(icon, tickerText, 0);
        mNotification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        backupToFile();
        mTimer.cancel();
        mTimer = null;
        mRevTimer.stop();
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return Service.START_STICKY;
    }

    @Override
    public void onStart(Intent intent, int startId){
        this.onStartCommand(intent, 0, startId);
    }

    private TimerTask mUpdateTask = new TimerTask() {
        @Override
        public void run() {
            long currentTime = System.currentTimeMillis();
            
            int ticks = (int)Math.round((currentTime - mLastTickTime) / 1000);

            for (int i = 0; i < ticks; i++) {
                mRevTimer.tick();
                mLastTickTime += 1000;
            }

            if (++sinceBackup >= 30) {
                backupToFile();
                sinceBackup = 0;
            }
        }
    };
    
    // IPC API
    private List<TimerListener> mListeners = new ArrayList<TimerListener>();
 

    private TimerApi.Stub apiEndpoint = new TimerApi.Stub() {
        public TimerState getTimerState() throws RemoteException {
            synchronized (mTimerState) {
                return mTimerState;
            }
        }

        public int getMode() throws RemoteException {
            synchronized (mRevTimer) {
                return mRevTimer.getMode();
            }
        }

        public void setMode(int mode) throws RemoteException {
            synchronized (mRevTimer) {
                mRevTimer.setMode(mode);
                switch(mRevTimer.getMode()) {
                case Timer.MODE_RELAX:
                case Timer.MODE_REVISE:
                	foreground();
                	break;
                case Timer.MODE_PAUSED:
                case Timer.MODE_OFF:
                	background();
                	break;
                }
            }
        }

        public float getRatio() throws RemoteException {
            synchronized (mRevTimer) {
                return mRevTimer.getRatio();
            }
        }

        public void setRatio(float ratio) throws RemoteException {
            synchronized (mRevTimer) {
                mRevTimer.setRatio(ratio);
            }
        }

        public void addListener(TimerListener listener)
            throws RemoteException {
            synchronized (mListeners) {
                mListeners.add(listener);
            }
        }

        public void removeListener(TimerListener listener)
            throws RemoteException {
            synchronized (mListeners) {
                mListeners.remove(listener);
            }
        }

        public void stop() {
            stopSelf();
        }

        public void reset() {
            mRevTimer.reset();
            deleteBackup();
        }

		public void setZeroRelaxAlarmPref(boolean state) throws RemoteException {
			mZeroRelaxAlarmPref = state;
		}
    };
    
    private void foreground() {
    	if (!isForeground) {
            forceUpdateNotification();
	        startForeground(NOTIFY_ID, mNotification);
	        isForeground = true;
    	}

        updateNotification();
    }
    
    private void background() {
    	if (isForeground) {
    		stopForeground(true);
    		isForeground = false;
    	}
    }

    // Notification stuff
    void updateNotification() {
    	if (isForeground){
    		forceUpdateNotification();
    	}
    }
    
    void forceUpdateNotification() {
        Context context = getApplicationContext();
        CharSequence contentTitle = modeToString();
        CharSequence contentText;
        synchronized (mRevTimer) {
        	contentText = getString(R.string.revise_heading) + ": " + new TimeFormatter(mRevTimer.getReviseTime()) 
            		+ " " + getString(R.string.break_heading) + ": " + new TimeFormatter(mRevTimer.getRelaxTime());
		}
        Intent notificationIntent = new Intent(this, RevisionDroid.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
        mNotificationManager.notify(NOTIFY_ID, mNotification);
    }
    
    private String modeToString() {
        int mode;
        String string = "Error";
        synchronized (mRevTimer) {
            mode = mRevTimer.getMode();
        }
        switch (mode) {
            case Timer.MODE_OFF:    string = getString(R.string.mode_off);
                                    break;
            case Timer.MODE_REVISE: string = getString(R.string.mode_revise);
                                    break;
            case Timer.MODE_RELAX:  string = getString(R.string.mode_relax);
                                    break;
            case Timer.MODE_PAUSED: string = getString(R.string.mode_paused);
                                    break;
        }
        return string;
    }

	void zeroRelaxAlarm() {
		if (!mZeroRelaxAlarmPref) {
			return;
		}
		
		Context context = getApplicationContext();
		CharSequence alarmTitle = getString(R.string.alarm_zerorelax_title);
		CharSequence contentText = getString(R.string.alarm_zerorelax_message);
		Intent notificationIntent = new Intent(this, RevisionDroid.class);
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
		
        int icon = R.drawable.ic_stat_alarm;
        CharSequence tickerText = getString(R.string.alarm_zerorelax_ticker);
        long when = System.currentTimeMillis();

		Notification notification = new Notification(icon, tickerText, when);
		notification.setLatestEventInfo(context, alarmTitle, contentText, contentIntent);
		notification.defaults |= Notification.DEFAULT_SOUND | Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE; 
		notification.flags |= Notification.FLAG_SHOW_LIGHTS | Notification.FLAG_AUTO_CANCEL;
		mNotificationManager.notify(ZERO_RELAX_ALARM_ID, notification);
	}

    private void backupToFile() {
        FileOutputStream fos;
        DataOutputStream dos;
        try {
            fos = openFileOutput(BACKUPFILE, Context.MODE_PRIVATE);
            dos = new DataOutputStream(fos);
            dos.writeInt(mRevTimer.getReviseTime());
            dos.writeFloat(mRevTimer.getRelaxTime());
        } catch (IOException e) {
            Log.e(TAG, "Failed to write backup.");
        }
    }
    
    private Timer readBackupFile(TimerEventHandler eventHandler) {
        FileInputStream fis;
        DataInputStream dis;
        try {
            fis = openFileInput(BACKUPFILE);
            dis = new DataInputStream(fis);
            int revise = dis.readInt();
            float relax = dis.readFloat();
            Timer timer = new Timer(eventHandler, revise, relax);
            return timer;
        } catch (FileNotFoundException e) {
            return new Timer(eventHandler);
        } catch (IOException e) {
            Log.e(TAG, "Failed to read backup file.");
            return new Timer(eventHandler);
        }
    }
    
    private void deleteBackup(){
        deleteFile(BACKUPFILE);
    }
    
    void contactListeners() {
        synchronized (mListeners) {
            for (TimerListener listener : mListeners) {
                try {
                    listener.handleTick();
                } catch (RemoteException e) {
                    Log.w(TAG, "Failed to notify listener " + listener, e);
                    mListeners.remove(listener);
                }
            }
        }
    }
}
